/**
 * Multicast project Bidois Morgan & Thomas Nunes
 *
 */
package middleware;

import Engine.Message;
import Test.I_RecvMsgHandler;
import Engine.NioEngine;
import Engine.TypeMessage;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author thomas
 *
 * The abstract class of the Server and client multicast layer
 */
public abstract class AbstractMulticast extends Thread implements I_RecvMulticastHandler {

    public boolean DEBUG = true;
    protected boolean burstmode = false;
    protected NioEngine engine;
    protected Directory directory;
    protected int idClient;
    protected String name;
    protected int logicalClock = 0;
    protected I_RecvMsgHandler handler;
    protected DataMulticastManager historyMessage = new DataMulticastManager();
    protected List<Integer> listOfAfkClient = new LinkedList<>();
    protected ArrayList< SocketChannel> clientList = new ArrayList<>();           //List of all clients connected

    public AbstractMulticast() {
        this.directory = new Directory();
        this.engine = new NioEngine();

        //We get the id of the JVM to identifie the client.
        String idProcessClient = ManagementFactory.getRuntimeMXBean().getName();
        String[] parts = idProcessClient.split("@");
        String strIdClient = parts[0];
        this.idClient = Integer.parseInt(strIdClient);
    }

    /**
     * send a NORMAL message to the client
     *
     * @param data the message
     */
    public void send(byte[] data) {
        this.send(idClient, data, TypeMessage.NORMAL);
    }

    /**
     * send a message to a client identifed by his id.
     *
     * @param idClient the receiver
     * @param data the message
     */
    public void send(int idCl, byte[] data, TypeMessage type) {
        this.logicalClock++;
        SocketChannel clientChannel = this.directory.getClientAt(idClient).getSocketChannel();
        engine.send(clientChannel, data, type, idCl, this.logicalClock);
    }

    @Override
    public void run() {
        engine.mainloop();
    }

    public void InitializeAsServer(InetAddress hostAddress, int port, I_RecvMsgHandler handler, I_ServerMulticast action) throws IOException {
        this.handler = handler;
        engine.InitializeAsServer(hostAddress, port, this, action);
    }

    public void InitializeAsClient(InetAddress hostAddress, int port, I_RecvMsgHandler handler, I_ClientMulticast action) throws IOException {
        this.handler = handler;
        engine.InitializeAsClient(hostAddress, port, this, action);
    }

    /**
     * Method use to send our Id to the server to register it. Do after the
     * connect Call in ExecuteClient- method register
     *
     * @param name the name of the client
     */
    public void registerAsClient(String clName) {
        if (!"".equals(clName)) {
            this.name = clName;
        } else {
            this.name = "anonymous" + idClient;
        }
        String strId = "" + idClient + ":" + name;
        this.engine.send(strId.getBytes(), TypeMessage.SEND_ID, idClient, this.logicalClock);
    }

    public void terminate() {
        engine.terminate();
    }

    /**
     * deliver a debug message. Print nothing if debug mode not activate
     *
     * @param message
     */
    protected void deliverDebugMessage(Message message) {
        //Just to print a DEBUG message
        if (this.DEBUG) {
            String msg = "DEBUG= " + message.getHeader().getType() + new String(message.getContent());
            this.handler.deliverCB(msg.getBytes());
        }
    }

    /**
     * deliver a message to the GUI
     *
     * @param message the message to deliver
     */
    protected void deliver(Message message) {
        this.handler.deliverCB(message.getContent());
    }

    @Override
    public void notifyAClientIsAFK(int idCl, SocketChannel socketChannel) {
        //We add to a treatement list the client
        this.listOfAfkClient.add(idCl);
        this.clientList.remove(socketChannel);
    }

    @Override
    public void removeBySocket(SocketChannel localSocketChannel) {
        this.clientList.remove(localSocketChannel);
    }

    @Override
    public void putSocket(SocketChannel localSocketChannel) {
        this.clientList.add(localSocketChannel);
    }

    @Override
    public void send2All(byte[] data, TypeMessage typeMessage) {
        ArrayList< SocketChannel> localList = (ArrayList< SocketChannel>) this.clientList.clone();
        for (SocketChannel so : localList) {
            this.engine.send(so, data, typeMessage, idClient, logicalClock);
        }
        //If some client are dead, we alert all the group
        Object[] listAFK =  this.listOfAfkClient.toArray();
        for (Object afkClientId : listAFK) { //For all dead client:
            this.engine.send(("" + afkClientId).getBytes(), TypeMessage.SOMEONE_LEAVE, idClient, logicalClock);
            for (SocketChannel so : clientList) {
                this.engine.send(so, ("" + afkClientId).getBytes(), TypeMessage.SOMEONE_LEAVE, idClient, logicalClock);
            }
            this.removeAclient((int) afkClientId);
        }
        this.listOfAfkClient = new LinkedList<>();
    }

    /**
     * Remove a dead client
     *
     * @param afkClientId
     */
    protected void removeAclient(int afkClientId) {
        if (this.directory.getClientById(afkClientId) != null) {
            if (this.directory.getClientById(afkClientId).getState() == 1) {        //if the client is alive
                ArrayList<DataMulticast> message2deliver = this.historyMessage.notifyClientLeave(afkClientId);

                //We deliver all message which waited this client
                for (DataMulticast dm : message2deliver) {
                    dm.setDeliver(true);
                    if (!dm.isDeliver()) {
                        this.deliver(dm.getMessage());
                        dm.setDeliver(true);
                    }
                }
            }
            //Remove from history
            this.directory.notifyClientLeave(afkClientId);
        }

    }
}
